/**
 *
 * This class represents the true engine of the AI.
 * It's supposed to find a optimal move to every game situation.
 *
 */
package AIEngine;

import java.util.LinkedList;
import java.util.ListIterator;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.Timer;

@SuppressWarnings({"unused", "unchecked"})
public class AlphaBetaEngine implements AIEngine {

    private boolean camp;           // which player is represented by the class
    private int maxDepth;
    //private NodeComparator nComp;   // comparison of tow nodes by the value
    private Timer sTimer;           // timer for supervising the searchtime
    private int limitAlphaBeta;
    
    /**
     * Default constructor for the alphabeataengie
     *
     * <ul>
     * <li>timer is used.</li>
     * <li>searchdeep= 4.</li>
     * <li>Player = firstplayer.</
     * li>
     * </ul>
     * @see NodeComparator
     *
     */
    public AlphaBetaEngine() {
        camp = true;
        maxDepth = 3;
        limitAlphaBeta = 6;
      //nComp = new NodeComparator();
    }

    /**
     * Constructor for the alphabeataengie by commiting the playerside
     *
     * @see NodeComparator
     * @param black true = firstplayer, false = secondplayer.
     */
    public AlphaBetaEngine(boolean black) {
        camp = black;
        maxDepth = 3;
        limitAlphaBeta = 6;
       // nComp = new NodeComparator();
    }

    /**
     * Getter for camp
     * @return camp
     */
    public boolean getcamp() {
        return camp;
    }

    /**
     * Checks if the search is supposed to cancel.
     *
     * @param node current position
     * @return <b>true</b> canceling search, <b>false</b> going on search
     */
    public boolean cutOff(Node node) {

    	if(node.getTurnsLeft() <= limitAlphaBeta)
    		maxDepth = limitAlphaBeta;

        if (node.getDepth() >= maxDepth) {
            return true;
        }

        return false;

    }

    /**
     * getNodes detects all sequence positions for a node
     *
     * @see Node
     * @param node root node, hear starts the search
     * @return childslist a linkedlist filled with all sequence positions for the root node
     */
    public LinkedList getNodes(Node node) {
        LinkedList<Node> childslist = new LinkedList();
        int[][] workplace = new int[6][6];

        if (BasicAI.checkWin(node.getBoard(), node.getCamp()) == true) {
            return null;
        }

        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 6; j++) {
                if (BasicAI.isEmpty(node.getBoard()[i][j])) {
                    workplace = node.getBoard();

                    workplace = BasicAI.rotate(workplace, 1, 1);
                    childslist.add(new Node(node, new int[]{i, j, 1, 1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 1, -1);

                    workplace = BasicAI.rotate(workplace, 1, -1);
                    childslist.add(new Node(node, new int[]{i, j, 1, -1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 1, 1);

                    workplace = BasicAI.rotate(workplace, 2, -1);
                    childslist.add(new Node(node, new int[]{i, j, 2, -1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 2, 1);

                    workplace = BasicAI.rotate(workplace, 2, -1);
                    childslist.add(new Node(node, new int[]{i, j, 2, -1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 2, 1);

                    workplace = BasicAI.rotate(workplace, 3, -1);
                    childslist.add(new Node(node, new int[]{i, j, 3, -1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 3, 1);

                    workplace = BasicAI.rotate(workplace, 3, -1);
                    childslist.add(new Node(node, new int[]{i, j, 3, -1,}, workplace,node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 3, 1);

                    workplace = BasicAI.rotate(workplace, 4, -1);
                    childslist.add(new Node(node, new int[]{i, j, 4, -1,}, workplace, node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 4, 1);

                    workplace = BasicAI.rotate(workplace, 4, -1);
                    childslist.add(new Node(node, new int[]{i, j, 4, -1,}, workplace,node.getTurnsLeft()));
                    workplace = BasicAI.rotate(workplace, 4, 1);

                }
            }
        }

        return childslist;
    }

    /**
     * Takes a node and evaluates the given position from it.
     * 
     * BasicAI.checkWin
     * BasicAI.fullJudge
     * @param node
     * @return value
     */
    public long evalNode(Node node) {
    	
    	node.setTmpboard(node.getBoard());
        long value = 0;

        if (BasicAI.checkWin(node.getTmpboard(), node.getCamp()) == true) {
            value = 99999;
        }
        if (BasicAI.checkWin(node.getTmpboard(), node.getCamp()) == false) {
            value = -99999;
        }
        if(node.getTurnsLeft() > limitAlphaBeta) {
        	BasicAI.fullJudge(node.getTmpboard(), node.getCamp());
        	node.setValue(BasicAI.getfullValue(node.getTmpboard(), node.getCamp()));
        }
        node.setValue(node.getValue() + value);
        value = node.getValue();
        return value;
    }

    /**
     * Setter for the depth
     * @param depth new value of depth
     */
    public void setDepth(int depth) {
        this.maxDepth = depth;
    }


    /**
     * Emits the best next possible move, deppending on the root node
     *
     * @see Node
     * @param node root for the calculation of the worst following move
     * @param alpha limit for the best possible move
     * @param beta limit for the best opponent move
     * @return next best move
     */
    public long maxNode(Node node, long alpha, long beta) {
        LinkedList childs;
        ListIterator iterator;
        Node tmpNode;
        long v = Long.MIN_VALUE;
        long value;

        if (cutOff(node)) {
            return evalNode(node);
        }

        childs = getNodes(node);


        if (childs == null) {
            return evalNode(node);
        }

        iterator = childs.listIterator(0);
        while (iterator.hasNext()) {
            tmpNode = (Node) iterator.next();

            value = minNode(tmpNode, alpha, beta);

            if (value >= v) {
                v = value;
            }

            if (v >= beta) {
                return v;
            }
            
            if(v >= alpha){
            	alpha = v;
            }  	
        }

        return v;
    }

    /**
     * Emits the worst next possible move, deppending on the root node
     *
     * @see Node
     * @param node root for the calculation of the worst following move
     * @param alpha limit for the best possible move
     * @param beta limit for the best opponent move
     * @return worst next possible move
     */
    public long minNode(Node node, long alpha, long beta) {
        LinkedList childs;
        ListIterator iterator;
        Node tmpNode;
        long v = Long.MAX_VALUE;
        long value;
        
        if (cutOff(node)) {
            return evalNode(node);
        }

        childs = getNodes(node);

        if (childs == null) {
            return evalNode(node);
        }

        iterator = childs.listIterator(0);
        while (iterator.hasNext()) {
            tmpNode = (Node) iterator.next();

            value = maxNode(tmpNode, alpha, beta);

            if (value <= v) {
                v = value;
            }

            if (v <= alpha) {
                return v;
            }
            
            if(v <= beta){
            	beta = v;
            }  	
        }

        return v;

    }

    /**
     * Searchs with the alpha-beta pruning a preferably and maybe best move.
     * Depending on the searchdepth and the timelimit
     *
     * @param node basic position for the serach
     * @return Node, the best move is returned as a node and <b>null</b> will
     * be returned if ther is no next move or we dont findet it
     */
    public Node getBestMove(Node node) {
        LinkedList childs;
        ListIterator iterator;
        Node tmpNode;
        Node bestNode;
        Node pdNode;
        long alpha;
        long beta;

        this.camp = node.getCamp();

        pdNode = null;

		alpha = Long.MIN_VALUE;
		beta = Long.MAX_VALUE;
		bestNode = null;
		childs = getNodes(node);
		if (childs == null) {
			return null;
		}

		iterator = childs.listIterator(0);
		while (iterator.hasNext()) {
			tmpNode = (Node) iterator.next();
			tmpNode.setValue(minNode(tmpNode, alpha, beta));

			if (tmpNode.getValue() > alpha) {
				alpha = tmpNode.getValue();
				bestNode = tmpNode;
			}

		}

        if (pdNode != null) {
            bestNode = pdNode;
        }

        bestNode.setDepth(0);

        return bestNode;

    }

}

